// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library IndexedDB1Test;

import 'package:expect/legacy/async_minitest.dart'; // ignore: deprecated_member_use
import 'dart:async';
import 'dart:html' as html;
import 'dart:math' as math;
import 'dart:indexed_db' as idb;

const String STORE_NAME = 'TEST';
const int VERSION = 1;

var databaseNameIndex = 0;
String nextDatabaseName() {
  return 'Test1_${databaseNameIndex++}';
}

Future testUpgrade() {
  var dbName = nextDatabaseName();
  var upgraded = false;

  // Delete any existing DBs.
  return html.window.indexedDB!
      .deleteDatabase(dbName)
      .then((_) {
        return html.window.indexedDB!.open(
          dbName,
          version: 1,
          onUpgradeNeeded: (e) {},
        );
      })
      .then((db) {
        db.close();
      })
      .then((_) {
        return html.window.indexedDB!.open(
          dbName,
          version: 2,
          onUpgradeNeeded: (e) {
            expect(e.oldVersion, 1);
            expect(e.newVersion, 2);
            upgraded = true;
          },
        );
      })
      .then((_) {
        expect(upgraded, isTrue);
      });
}

testReadWrite(
  key,
  value,
  matcher, [
  dbName,
  storeName = STORE_NAME,
  version = VERSION,
  stringifyResult = false,
]) => () {
  if (dbName == null) {
    dbName = nextDatabaseName();
  }
  createObjectStore(e) {
    idb.ObjectStore store = e.target.result.createObjectStore(storeName);
    expect(store, isNotNull);
  }

  late idb.Database db;
  return html.window.indexedDB!
      .deleteDatabase(dbName)
      .then((_) {
        return html.window.indexedDB!.open(
          dbName,
          version: version,
          onUpgradeNeeded: createObjectStore,
        );
      })
      .then((idb.Database result) {
        db = result;
        var transaction = db.transactionList([storeName], 'readwrite');
        transaction.objectStore(storeName).put(value, key);
        return transaction.completed;
      })
      .then((_) {
        var transaction = db.transaction(storeName, 'readonly');
        return transaction.objectStore(storeName).getObject(key);
      })
      .then((object) {
        db.close();
        if (stringifyResult) {
          // Stringify the numbers to verify that we're correctly returning ints
          // as ints vs doubles.
          expect(object.toString(), matcher);
        } else {
          expect(object, matcher);
        }
      })
      .whenComplete(() {
        return html.window.indexedDB!.deleteDatabase(dbName);
      });
};

testReadWriteTyped(
  key,
  value,
  matcher, [
  dbName,
  String storeName = STORE_NAME,
  version = VERSION,
  stringifyResult = false,
]) => () {
  if (dbName == null) {
    dbName = nextDatabaseName();
  }
  void createObjectStore(e) {
    var store = e.target.result.createObjectStore(storeName);
    expect(store, isNotNull);
  }

  late idb.Database db;
  // Delete any existing DBs.
  return html.window.indexedDB!
      .deleteDatabase(dbName)
      .then((_) {
        return html.window.indexedDB!.open(
          dbName,
          version: version,
          onUpgradeNeeded: createObjectStore,
        );
      })
      .then((idb.Database result) {
        db = result;
        idb.Transaction transaction = db.transactionList([
          storeName,
        ], 'readwrite');
        transaction.objectStore(storeName).put(value, key);

        return transaction.completed;
      })
      .then((idb.Database result) {
        idb.Transaction transaction = db.transaction(storeName, 'readonly');
        return transaction.objectStore(storeName).getObject(key);
      })
      .then((object) {
        db.close();
        if (stringifyResult) {
          // Stringify the numbers to verify that we're correctly returning ints
          // as ints vs doubles.
          expect(object.toString(), matcher);
        } else {
          expect(object, matcher);
        }
      })
      .whenComplete(() {
        return html.window.indexedDB!.deleteDatabase(dbName);
      });
};

void testTypes(testFunction) {
  test('String', testFunction(123, 'Hoot!', equals('Hoot!')));
  test('int', testFunction(123, 12345, equals(12345)));
  test('List', testFunction(123, [1, 2, 3], equals([1, 2, 3])));
  test('List 2', testFunction(123, [2, 3, 4], equals([2, 3, 4])));
  test('bool', testFunction(123, [true, false], equals([true, false])));
  test(
    'largeInt',
    testFunction(
      123,
      1371854424211,
      equals("1371854424211"),
      null,
      STORE_NAME,
      VERSION,
      true,
    ),
  );
  test(
    'largeDoubleConvertedToInt',
    testFunction(
      123,
      1371854424211.0,
      equals("1371854424211"),
      null,
      STORE_NAME,
      VERSION,
      true,
    ),
  );
  test(
    'largeIntInMap',
    testFunction(
      123,
      {'time': 4503599627370492},
      equals("{time: 4503599627370492}"),
      null,
      STORE_NAME,
      VERSION,
      true,
    ),
  );
  var now = new DateTime.now();
  test(
    'DateTime',
    testFunction(
      123,
      now,
      predicate(
        (date) => date.millisecondsSinceEpoch == now.millisecondsSinceEpoch,
      ),
    ),
  );
}

main() {
  // Test that indexed_db is properly flagged as supported or not.
  // Note that the rest of the indexed_db tests assume that this has been
  // checked.
  group('supported', () {
    test('supported', () {
      expect(idb.IdbFactory.supported, true);
    });
  });

  group('functional', () {
    test('throws when unsupported', () {
      var expectation = idb.IdbFactory.supported ? returnsNormally : throws;

      expect(() {
        var db = html.window.indexedDB!;
        db.open('random_db');
      }, expectation);
    });

    // Don't bother with these tests if it's unsupported.
    if (idb.IdbFactory.supported) {
      test('upgrade', testUpgrade);
      group('dynamic', () {
        testTypes(testReadWrite);
      });
      group('typed', () {
        testTypes(testReadWriteTyped);
      });
    }
  });
}
